package com.example.sample.logic;

import android.text.TextUtils;
import android.util.Log;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

public final class LogImpl {
	private static final String FILE_FORMAT = "yyyyMMdd";
	private static final String MESSAGE_FORMAT = "MM-dd HH:mm:ss.ms";
	private static final String SUFFIX = ".log";
	private static final String SEPARATOR = ".";

    private static String logPath;
	private static int level = Log.DEBUG;

	private static final DateFormat messageFormat = new SimpleDateFormat(MESSAGE_FORMAT, Locale.getDefault());
	private static final DateFormat fileNameFormat = new SimpleDateFormat(FILE_FORMAT, Locale.getDefault());

    private static final Executor logger = Executors.newSingleThreadExecutor(ConcurrentUtil.getDefaultThreadFactory("NimLogImpl"));

    public static void init(String logPath, int level) {
        LogImpl.logPath = logPath;
        LogImpl.level = level;
    }

	public static void i(String tag, String msg) {
		i(tag, msg, null);
	}

	public static void i(String tag, String msg, Throwable tr) {
        o(Log.INFO, tag, msg, tr);
	}

	public static void v(String tag, String msg) {
		v(tag, msg, null);
	}

	public static void v(String tag, String msg, Throwable tr) {
        o(Log.VERBOSE, tag, msg, tr);
	}

	public static void e(String tag, String msg) {
		e(tag, msg, null);
	}

	public static void e(String tag, String msg, Throwable tr) {
        o(Log.ERROR, tag, msg, tr);
	}

	public static void d(String tag, String msg) {
		d(tag, msg, null);
	}

	public static void d(String tag, String msg, Throwable tr) {
        o(Log.DEBUG, tag, msg, tr);
	}

	public static void w(String tag, String msg) {
		w(tag, msg, null);
	}

	public static void w(String tag, String msg, Throwable tr) {
        o(Log.WARN, tag, msg, tr);
	}
	
	public static void o(int priority, String tag, String msg) { 
		o(priority, tag, msg, null);
	}
	
	public static void o(final int priority, final String tag, final String msg, final Throwable tr) {
        final long time = System.currentTimeMillis();
        final long threadId = Thread.currentThread().getId();
        logger.execute(new Runnable() {
            @Override
            public void run() {
                String timeStr = messageFormat.format(new Date(time));
                Log.println(priority, tag, threadId + "/" + msg + '\n' + Log.getStackTraceString(tr));

                if (level <= priority) {
                    outMessage(tag, timeStr, msg, tr);
                }
            }
        });
	}

	public static void oSync(final int priority, final String tag, final String msg, final Throwable tr) {
		final long time = System.currentTimeMillis();
		final long threadId = Thread.currentThread().getId();
		String timeStr = messageFormat.format(new Date(time));
		Log.println(priority, tag, threadId + "/" + msg + '\n' + Log.getStackTraceString(tr));

		if (level <= priority) {
			outMessage(tag, timeStr, msg, tr);
		}
	}
	
	public static void setLevel(int level) {
		LogImpl.level = level;
	}

	private static void outMessage(String tag, String time, String msg, Throwable tr) {
		outMessage("", tag, time, msg, tr);
	}

	private static void outMessage(String cat, String tag, String time, String msg, Throwable tr) {
		if (TextUtils.isEmpty(logPath)) {
			return;
		}

		outputToFile(formatMessage(tag, time, msg, tr), getLogFilePath(cat));
	}
	
	private static String formatMessage(String tag, String time, String msg, Throwable tr) {
		StringBuilder sb = new StringBuilder();
		
		// time
		sb.append(time);
		sb.append(": ");
		
		// tag
		sb.append(tag);
		sb.append(": ");

		// message
		sb.append(msg);
		sb.append("\n");
		
		// Throwable
		if (tr != null) {
			sb.append(Log.getStackTraceString(tr));
			sb.append("\n");
		}
		
		return sb.toString();
	}

	private static boolean outputToFile(String message, String path) {
		if (TextUtils.isEmpty(message)) {
			return false;
		}
		
		if (TextUtils.isEmpty(path)) {
			return false;
		}
		
		boolean written = false;
		try {
            BufferedWriter fw = new BufferedWriter(new FileWriter(path, true));
			fw.write(message);
			fw.flush();
			fw.close();
			
			written = true;
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return written;
	}
	
	/*package*/ static String getLogFileName(String cat) {
		StringBuilder sb = new StringBuilder();
		
		sb.append(fileNameFormat.format(new Date()));
		
		if (!TextUtils.isEmpty(cat)) {
			sb.append(SEPARATOR);
			sb.append(cat);
		}
		
		sb.append(SUFFIX);
		
		return sb.toString();
	}

    private static String getLogFilePath(String cat) {
        File dir = new File(logPath);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        return logPath + "/" + getLogFileName(cat);
    }

	public static class ConcurrentUtil {
		public ConcurrentUtil() {
		}

		public static ThreadFactory getDefaultThreadFactory(String name) {
			return new ConcurrentUtil.XThreadFactory(name);
		}

		private static class XThreadFactory implements ThreadFactory {
			private final ThreadGroup mThreadGroup;
			private final AtomicInteger mThreadNumber;
			private final String mNamePrefix;

			private XThreadFactory(String name) {
				this.mThreadNumber = new AtomicInteger(1);
				SecurityManager s = System.getSecurityManager();
				this.mThreadGroup = s != null ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
				this.mNamePrefix = name + "#";
			}

			public Thread newThread(Runnable r) {
				Thread t = new Thread(this.mThreadGroup, r, this.mNamePrefix + this.mThreadNumber.getAndIncrement(), 0L);
				if (t.isDaemon()) {
					t.setDaemon(false);
				}

				if (t.getPriority() != 5) {
					t.setPriority(5);
				}

				return t;
			}
		}
	}
}